/// @file Configuration.cpp
/// @brief static class, read the xml config file and initialises all the
/// the objects and managers used in the flocking system

#include <iostream>
#include "QString"

#include "pugixml.hpp"

#include "boost/tokenizer.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>

#include "ngl/Colour.h"

#include "Configuration.h"


//static variables initialisation
std::string Configuration::s_settingsDoc = "config/Settings.xml";
bool Configuration::s_debug = true;
float Configuration::s_accelerationOfFreeFall = -9.8;

ngl::Colour Configuration::s_normalColour(0, 0, 1);
ngl::Colour Configuration::s_mainTraceColour(1, 1, 1);
ngl::Colour Configuration::s_neighbourTraceColour(0, 0, 1);
ngl::Colour Configuration::s_structuralNeighbourTraceColour(0, 1, 0);
ngl::Colour Configuration::s_shearNeighbourTraceColour(0, 1, 1);
ngl::Colour Configuration::s_flexNeighbourTraceColour(1, 0, 1);

std::vector<float> Configuration::getFloatVectorFromString
                                            (
                                                const std::string _str,
                                                const int _d
                                            )
{
    //split string into array of strings by ","
    std::vector<std::string> strArray;
    boost::split(strArray, _str, boost::is_any_of(","));

    //loop through strings and convert to float array
    std::vector<float> floatArray;
    for (int i = 0; i < _d; i++) floatArray.push_back(boost::lexical_cast<float>(strArray[i]));

    //return array of float
    return floatArray;
}

void Configuration::initialiseWindow
                                (
                                    int &o_frameRate,
                                    int &o_mouseMoveSensitivity
                                )
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //load global settings here
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Global");

    //read fps params
    o_frameRate = node.first().node().attribute("fpsFrequency").as_int();

    //read fps params
    o_mouseMoveSensitivity = node.first().node().attribute("mouseSensitivity").as_int();

}

ngl::Camera Configuration::initialiseCamera(const float _aspect)
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //get camera node
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Camera");

    //get near and far plane values
    float near = node.first().node().attribute("near").as_float();
    float far = node.first().node().attribute("far").as_float();

    //get fov
    int fov = node.first().node().attribute("fov").as_int();

    //get from and to values and convert to float arrays
    std::vector<float> from = getFloatVectorFromString(node.first().node().attribute("from").value(), 3);
    std::vector<float> to = getFloatVectorFromString(node.first().node().attribute("to").value(), 3);

    //build vectors
    ngl::Vector eye(from[0], from[1], from[2]);
    ngl::Vector look(to[0], to[1], to[2]);
    ngl::Vector up(0, 1, 0);

    //create a camera with the read data
    ngl::Camera cam;
    cam.set(eye, look, up);
    cam.setShape(fov, _aspect, near, far, ngl::PERSPECTIVE);

    return cam;
}

ngl::Light Configuration::initialiseLight()
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //get camera node
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Light");

    //get to values and convert to float array
    std::vector<float> to = getFloatVectorFromString(node.first().node().attribute("to").value(), 3);

    //get color values and convert to float array
    std::vector<float> color = getFloatVectorFromString(node.first().node().attribute("color").value(), 4);

    //build vectors
    ngl::Vector direction(to[0], to[1], to[2]);
    ngl::Colour lightColor(color[0], color[1], color[2], color[3]);

    //create a light with the read data
    ngl::Light light(direction, lightColor, ngl::LIGHTREMOTE);

    return light;
}

std::vector<ShaderObject*>* Configuration::initialiseShader()
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    std::vector<ShaderObject*>* result = new std::vector<ShaderObject*>;

    //get list of normal shaders
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Shaders/Shader");
    for (pugi::xpath_node_set::const_iterator it = node.begin(); it != node.end(); ++it)
    {
        //get details for a shader
        pugi::xpath_node node = *it;

        //save values in array
        std::string name = node.node().attribute("name").value();
        std::string vs = node.node().attribute("vertex").value();
        std::string fs = node.node().attribute("fragment").value();
        bool isSpecial = node.node().attribute("isSpecial").as_bool();

        std::vector<std::string> attribNames;
        std::vector<int> attribValues;

        if (isSpecial)
        {
            //load attributes
            pugi::xpath_node_set attributes = node.node().select_nodes("Attrib");
            for (pugi::xpath_node_set::const_iterator it1 = attributes.begin(); it1 != attributes.end(); ++it1)
            {
                pugi::xpath_node innerNode = *it1;

                std::string attribName = innerNode.node().attribute("name").value();
                int value = innerNode.node().attribute("value").as_int();

                attribNames.push_back(attribName);
                attribValues.push_back(value);
            }
        }

        //create a shader and add it to our list
        result->push_back(new ShaderObject(name, vs, fs, attribNames, attribValues, isSpecial));
    }

    return result;
}

std::vector<ClothMaterial*>* Configuration::initialiseClothMaterials()
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    std::vector<ClothMaterial*>* result = new std::vector<ClothMaterial*>;

    //get list of types
    pugi::xpath_node_set node = doc.select_nodes("/Settings/ClothTemplate/ClothItem");
    for (pugi::xpath_node_set::const_iterator it = node.begin(); it != node.end(); ++it)
    {
        //get details for a type
        pugi::xpath_node node = *it;

        //read params
        std::string name = node.node().attribute("name").value();
        std::string texName = node.node().attribute("filename").value();
        float stiffness = node.node().attribute("stiffness").as_float();
        float damping = node.node().attribute("damping").as_float();

        //create a new material and add the material to our list
        result->push_back(new ClothMaterial(name, texName, stiffness, damping));
    }

    return result;

}

void Configuration::initialiseObstacles
                                (
                                    bool &o_cubeEnabled,
                                    ngl::Vector &o_cubePosition,
                                    ngl::Vector &o_cubeDimension,
                                    bool &o_sphereEnabled,
                                    ngl::Vector &o_spherePosition,
                                    int &o_sphereRadius
                                )
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //load global settings here
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Obstacles");

    //read xml tags, convert to their appropriate formats and save into the reference variables
    o_cubeEnabled = node.first().node().select_single_node("Cube").node().attribute("enabled").as_bool();

    std::vector<float> pos = getFloatVectorFromString(node.first().node().select_single_node("Cube").node().attribute("position").value(), 3);
    o_cubePosition.set(pos[0], pos[1], pos[2]);

    std::vector<float> dimension = getFloatVectorFromString(node.first().node().select_single_node("Cube").node().attribute("dimension").value(), 3);
    o_cubeDimension.set(dimension[0], dimension[1], dimension[2]);

    o_sphereEnabled = node.first().node().select_single_node("Sphere").node().attribute("enabled").as_bool();

    pos = getFloatVectorFromString(node.first().node().select_single_node("Sphere").node().attribute("position").value(), 3);
    o_spherePosition.set(pos[0], pos[1], pos[2]);

    o_sphereRadius = node.first().node().select_single_node("Sphere").node().attribute("radius").as_int();

}
